title: 对象的扩展
date: 2018.9.7
tags:


2018.9.7 星期五 9:46

1. 属性的简洁表示法
2. 属性名表达式
3. 方法的 name 属性
4. Object.is()
5. Object.assign()
6. 属性的可枚举性和遍历
7. Object.getOwnPropertyDescriptors()
8. proto属性,Object.setPrototypeOf(),Object.getPrototypeOf()
9. super 关键字
10. Object.keys(),Object.values(),Object.entries()
11. 对象的扩展运算符

1. 属性的简洁表示法

ES6 允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值。
除了属性简写,方法也可以简写。

CommonJS 模块输出一组变量,就非常合适使用简洁写法。
属性的赋值器(setter)和取值器(getter),事实上也是采用这种写法。

注意,简洁写法的属性名总是字符串,这会导致一些看上去比较奇怪的结果。
如果某个方法的值是一个 Generator 函数,前面需要加上星号。

let birth = '2000/01/01';
const Person = {
  name: '张三',
  birth,
  hello() { console.log('我的名字是', this.name); }
};

// 总是字符串
const obj = {
  class () {}
};
// 等同于
var obj = {
  'class': function() {}
};

2. 属性名表达式

JavaScript 定义对象的属性,有两种方法。
但是,如果使用字面量方式定义对象(使用大括号),在 ES5 中只能使用方法一(标识符)定义属性。
ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。
表达式还可以用于定义方法名。

obj.foo = true;
obj['a' + 'bc'] = 123;
// ES6
let propKey = 'foo';

let obj = {
  [propKey]: true,
  ['a' + 'bc']: 123,
  ['h' + 'ello']() {
    return 'hi';
  }
};

注意,属性名表达式与简洁表示法,不能同时使用,会报错。
注意,属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object],这一点要特别小心。


// ### 是对象
const keyA = {a: 1};
const keyB = {b: 2};
const myObject = {
  [keyA]: 'valueA',
  [keyB]: 'valueB'
};
myObject // Object {[object Object]: "valueB"}

3. 方法的 name 属性

如果对象的方法使用了取值函数(getter)和存值函数(setter),则name属性不是在该方法上面,而是该方法的属性的描述对象的get和set属性上面,返回值是方法名前加上get和set。

有两种特殊情况:bind方法创造的函数,name属性返回bound加上原函数的名字;
Function构造函数创造的函数,name属性返回anonymous。

如果对象的方法是一个 Symbol 值,那么name属性返回的是这个 Symbol 值的描述。

const obj = {
  get foo() {},
  set foo(x) {}
};
obj.foo.name
// TypeError: Cannot read property 'name' of undefined
const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
descriptor.get.name // "get foo"
descriptor.set.name // "set foo"

// 特殊情况
(new Function()).name // "anonymous"
var doSomething = function() {
  // ...
};
doSomething.bind().name // "bound doSomething"

4. Object.is()

ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。
它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。

ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

不同之处只有两个:一是+0不等于-0,二是NaN等于自身。

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

5. Object.assign()

基本用法

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。
属性名为 Symbol 值的属性,也会被Object.assign拷贝。

####
如果只有一个参数,Object.assign会直接返回该参数。
如果该参数不是对象,则会先转成对象,然后返回。
由于undefined和null无法转成对象,所以如果它们作为参数,就会报错。
####
如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefined和null不在首参数,就不会报错。

其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。
这是因为只有字符串的包装对象,会产生可枚举属性。

let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true
// ### 
const v1 = 'abc';
const v2 = true;
const v3 = 10;
const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
// ### 
Object.assign({b: 'c'},
  Object.defineProperty({}, 'invisible', {
    enumerable: false,
    value: 'hello'
  })
)
// { b: 'c' }

var a=10;
funciton f(){
    console.log(a)
    var a=2;
}
f();

注意点

(1)浅拷贝
(2)同名属性的替换
(3)数组的处理:Object.assign可以用来处理数组,但是会把数组视为对象。
(4)取值函数的处理:Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。

Object.assign([1, 2, 3], [4, 5]) // [4, 5, 3]
// #### 取值函数    
const source = {
  get foo() { return 1 }
};
const target = {};
Object.assign(target, source)// { foo: 1 }

常见用途

(1)为对象添加属性
(2)为对象添加方法
(3)克隆对象
(4)合并多个对象
(5)为属性指定默认值:存在浅拷贝的问题,最好都是简单类型,

6. 属性的可枚举性和遍历

可枚举性

对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。

目前,有四个操作会忽略enumerable为false的属性。

(2)Object.keys(obj)
Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。

(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。

(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。

(5)Reflect.ownKeys(obj)
Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。

以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
首先遍历所有数值键,按照数值升序排列。
其次遍历所有字符串键,按照加入时间升序排列。
最后遍历所有 Symbol 键,按照加入时间升序排列。

7. Object.getOwnPropertyDescriptors()

前面说过,Object.getOwnPropertyDescriptor方法会返回某个对象属性的描述对象
ES2017 引入了

该方法的引入目的,主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题。
方法的另一个用处,是配合Object.create方法,将对象属性克隆到一个新对象。这属于浅拷贝。
可以实现一个对象继承另一个对象。
也可以用来实现 Mixin(混入)模式。

8. proto属性,Object.setPrototypeOf(),Object.getPrototypeOf()

proto属性

proto属性(前后各两个下划线),用来读取或设置当前对象的prototype对象。目前,所有浏览器(包括 IE11)都部署了这个属性。
该属性没有写入 ES6 的正文,而是写入了附录,原因
标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的。
因此,无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,而是使用下面的Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。

实现上,proto调用的是Object.prototype.proto,具体实现如下。

Object.setPrototypeOf()

作用与proto相同,用来设置一个对象的prototype对象,返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。

如果第一个参数不是对象,会自动转为对象。但是由于返回的还是第一个参数,所以这个操作不会产生任何效果。
由于undefined和null无法转为对象,所以如果第一个参数是undefined或null,就会报错。

Object.getPrototypeOf()

如果参数不是对象,会被自动转为对象。
如果参数是undefined或null,它们无法转为对象,所以会报错。

9. super 关键字

ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。

注意,super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。
目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法。

上面代码中,super.foo指向原型对象proto的foo方法,但是绑定的this却还是当前对象obj,因此输出的就是world。

10. Object.keys(),Object.values(),Object.entries()

Object.keys()

ES5 引入了Object.keys方法,
返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的 键名/键值/键值对
ES2017 引入了跟Object.keys配套的Object.values和Object.entries,作为遍历一个对象的补充手段,供for…of循环使用。

Object.values()

返回数组的成员顺序,与本章的《属性的遍历》部分介绍的排列规则一致。
Object.values会过滤属性名为 Symbol 值的属性。
Object.values会过滤属性名为 Symbol 值的属性。符串会先转成一个类似数组的对象
如果参数不是对象,Object.values会先将其转为对象。由于数值和布尔值的包装对象,都不会为实例添加非继承的属性。所以,Object.values会返回空数组。

Object.entries()

行为与Object.values基本一致。
如果原对象的属性名是一个 Symbol 值,该属性会被忽略。

Object.entries的基本用途是遍历对象的属性。
Object.entries方法的另一个用处是,将对象转为真正的Map结构。

11. 对象的扩展运算符

ES2018 将这个运算符引入了对象。

解构赋值

由于解构赋值要求等号右边是一个对象,所以如果等号右边是undefined或null,就会报错,因为它们无法转为对象。
解构赋值必须是最后一个参数,否则会报错。

注意,解构赋值的拷贝是浅拷贝
另外,扩展运算符的解构赋值,不能复制继承自原型对象的属性。
解构赋值的一个用处,是扩展某个函数的参数,引入其他操作。

扩展运算符

$_LUE:

11:13